#region Apache License
//
// Licensed to the Apache Software Foundation (ASF) under one or more 
// contributor license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright ownership. 
// The ASF licenses this file to you under the Apache License, Version 2.0
// (the "License"); you may not use this file except in compliance with 
// the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#endregion

using log4net.Repository;
using log4net.Util;
using System;
using System.Collections;
using System.Reflection;


namespace log4net.Core
{
    /// <summary>
    /// The implementation of the <see cref="IRepositorySelector"/> interface suitable
    /// for use with the compact framework
    /// </summary>
    /// <remarks>
    /// <para>
    /// This <see cref="IRepositorySelector"/> implementation is a simple
    /// mapping between repository name and <see cref="ILoggerRepository"/>
    /// object.
    /// </para>
    /// <para>
    /// The .NET Compact Framework 1.0 does not support retrieving assembly
    /// level attributes therefore unlike the <c>DefaultRepositorySelector</c>
    /// this selector does not examine the calling assembly for attributes.
    /// </para>
    /// </remarks>
    /// <author>Nicko Cadell</author>
    public class CompactRepositorySelector : IRepositorySelector
    {
        #region Member Variables

        private const string DefaultRepositoryName = "log4net-default-repository";

        private readonly Hashtable m_name2repositoryMap = new Hashtable();
        private readonly Type m_defaultRepositoryType;

        private event LoggerRepositoryCreationEventHandler m_loggerRepositoryCreatedEvent;

        #endregion

        #region Constructors

        /// <summary>
        /// Create a new repository selector
        /// </summary>
        /// <param name="defaultRepositoryType">the type of the repositories to create, must implement <see cref="ILoggerRepository"/></param>
        /// <remarks>
        /// <para>
        /// Create an new compact repository selector.
        /// The default type for repositories must be specified,
        /// an appropriate value would be <see cref="log4net.Repository.Hierarchy.Hierarchy"/>.
        /// </para>
        /// </remarks>
        /// <exception cref="ArgumentNullException">throw if <paramref name="defaultRepositoryType"/> is null</exception>
        /// <exception cref="ArgumentOutOfRangeException">throw if <paramref name="defaultRepositoryType"/> does not implement <see cref="ILoggerRepository"/></exception>
        public CompactRepositorySelector(Type defaultRepositoryType)
        {
            if (defaultRepositoryType == null)
            {
                throw new ArgumentNullException("defaultRepositoryType");
            }

            // Check that the type is a repository
            if (!(typeof(ILoggerRepository).IsAssignableFrom(defaultRepositoryType)))
            {
                throw log4net.Util.SystemInfo.CreateArgumentOutOfRangeException("defaultRepositoryType", (object)defaultRepositoryType, "Parameter: defaultRepositoryType, Value: [" + defaultRepositoryType + "] out of range. Argument must implement the ILoggerRepository interface");
            }

            m_defaultRepositoryType = defaultRepositoryType;

            LogLog.Debug(declaringType, "defaultRepositoryType [" + m_defaultRepositoryType + "]");
        }

        #endregion

        #region Implementation of IRepositorySelector

        /// <summary>
        /// Get the <see cref="ILoggerRepository"/> for the specified assembly
        /// </summary>
        /// <param name="assembly">not used</param>
        /// <returns>The default <see cref="ILoggerRepository"/></returns>
        /// <remarks>
        /// <para>
        /// The <paramref name="assembly"/> argument is not used. This selector does not create a
        /// separate repository for each assembly. 
        /// </para>
        /// <para>
        /// As a named repository is not specified the default repository is 
        /// returned. The default repository is named <c>log4net-default-repository</c>.
        /// </para>
        /// </remarks>
        public ILoggerRepository GetRepository(Assembly assembly)
        {
            return CreateRepository(assembly, m_defaultRepositoryType);
        }

        /// <summary>
        /// Get the named <see cref="ILoggerRepository"/>
        /// </summary>
        /// <param name="repositoryName">the name of the repository to lookup</param>
        /// <returns>The named <see cref="ILoggerRepository"/></returns>
        /// <remarks>
        /// <para>
        /// Get the named <see cref="ILoggerRepository"/>. The default 
        /// repository is <c>log4net-default-repository</c>. Other repositories 
        /// must be created using the <see cref="CreateRepository(string, Type)"/>.
        /// If the named repository does not exist an exception is thrown.
        /// </para>
        /// </remarks>
        /// <exception cref="ArgumentNullException">throw if <paramref name="repositoryName"/> is null</exception>
        /// <exception cref="LogException">throw if the <paramref name="repositoryName"/> does not exist</exception>
        public ILoggerRepository GetRepository(string repositoryName)
        {
            if (repositoryName == null)
            {
                throw new ArgumentNullException("repositoryName");
            }

            lock (this)
            {
                // Lookup in map
                ILoggerRepository rep = m_name2repositoryMap[repositoryName] as ILoggerRepository;
                if (rep == null)
                {
                    throw new LogException("Repository [" + repositoryName + "] is NOT defined.");
                }
                return rep;
            }
        }

        /// <summary>
        /// Create a new repository for the assembly specified 
        /// </summary>
        /// <param name="assembly">not used</param>
        /// <param name="repositoryType">the type of repository to create, must implement <see cref="ILoggerRepository"/></param>
        /// <returns>the repository created</returns>
        /// <remarks>
        /// <para>
        /// The <paramref name="assembly"/> argument is not used. This selector does not create a
        /// separate repository for each assembly. 
        /// </para>
        /// <para>
        /// If the <paramref name="repositoryType"/> is <c>null</c> then the
        /// default repository type specified to the constructor is used.
        /// </para>
        /// <para>
        /// As a named repository is not specified the default repository is 
        /// returned. The default repository is named <c>log4net-default-repository</c>.
        /// </para>
        /// </remarks>
        public ILoggerRepository CreateRepository(Assembly assembly, Type repositoryType)
        {
            // If the type is not set then use the default type
            if (repositoryType == null)
            {
                repositoryType = m_defaultRepositoryType;
            }

            lock (this)
            {
                // This method should not throw if the default repository already exists.

                // First check that the repository does not exist
                ILoggerRepository rep = m_name2repositoryMap[DefaultRepositoryName] as ILoggerRepository;
                if (rep == null)
                {
                    // Must create the repository
                    rep = CreateRepository(DefaultRepositoryName, repositoryType);
                }

                return rep;
            }
        }

        /// <summary>
        /// Create a new repository for the repository specified
        /// </summary>
        /// <param name="repositoryName">the repository to associate with the <see cref="ILoggerRepository"/></param>
        /// <param name="repositoryType">the type of repository to create, must implement <see cref="ILoggerRepository"/>.
        /// If this param is null then the default repository type is used.</param>
        /// <returns>the repository created</returns>
        /// <remarks>
        /// <para>
        /// The <see cref="ILoggerRepository"/> created will be associated with the repository
        /// specified such that a call to <see cref="GetRepository(string)"/> with the
        /// same repository specified will return the same repository instance.
        /// </para>
        /// <para>
        /// If the named repository already exists an exception will be thrown.
        /// </para>
        /// <para>
        /// If <paramref name="repositoryType"/> is <c>null</c> then the default 
        /// repository type specified to the constructor is used.
        /// </para>
        /// </remarks>
        /// <exception cref="ArgumentNullException">throw if <paramref name="repositoryName"/> is null</exception>
        /// <exception cref="LogException">throw if the <paramref name="repositoryName"/> already exists</exception>
        public ILoggerRepository CreateRepository(string repositoryName, Type repositoryType)
        {
            if (repositoryName == null)
            {
                throw new ArgumentNullException("repositoryName");
            }

            // If the type is not set then use the default type
            if (repositoryType == null)
            {
                repositoryType = m_defaultRepositoryType;
            }

            lock (this)
            {
                ILoggerRepository rep = null;

                // First check that the repository does not exist
                rep = m_name2repositoryMap[repositoryName] as ILoggerRepository;
                if (rep != null)
                {
                    throw new LogException("Repository [" + repositoryName + "] is already defined. Repositories cannot be redefined.");
                }
                else
                {
                    LogLog.Debug(declaringType, "Creating repository [" + repositoryName + "] using type [" + repositoryType + "]");

                    // Call the no arg constructor for the repositoryType
                    rep = (ILoggerRepository)Activator.CreateInstance(repositoryType);

                    // Set the name of the repository
                    rep.Name = repositoryName;

                    // Store in map
                    m_name2repositoryMap[repositoryName] = rep;

                    // Notify listeners that the repository has been created
                    OnLoggerRepositoryCreatedEvent(rep);
                }

                return rep;
            }
        }

        /// <summary>
        /// Test if a named repository exists
        /// </summary>
        /// <param name="repositoryName">the named repository to check</param>
        /// <returns><c>true</c> if the repository exists</returns>
        /// <remarks>
        /// <para>
        /// Test if a named repository exists. Use <see cref="CreateRepository(string, Type)"/>
        /// to create a new repository and <see cref="GetRepository(string)"/> to retrieve 
        /// a repository.
        /// </para>
        /// </remarks>
        public bool ExistsRepository(string repositoryName)
        {
            lock (this)
            {
                return m_name2repositoryMap.ContainsKey(repositoryName);
            }
        }

        /// <summary>
        /// Gets a list of <see cref="ILoggerRepository"/> objects
        /// </summary>
        /// <returns>an array of all known <see cref="ILoggerRepository"/> objects</returns>
        /// <remarks>
        /// <para>
        /// Gets an array of all of the repositories created by this selector.
        /// </para>
        /// </remarks>
        public ILoggerRepository[] GetAllRepositories()
        {
            lock (this)
            {
                ICollection reps = m_name2repositoryMap.Values;
                ILoggerRepository[] all = new ILoggerRepository[reps.Count];
                reps.CopyTo(all, 0);
                return all;
            }
        }

        #endregion

        #region Private Static Fields

        /// <summary>
        /// The fully qualified type of the CompactRepositorySelector class.
        /// </summary>
        /// <remarks>
        /// Used by the internal logger to record the Type of the
        /// log message.
        /// </remarks>
        private readonly static Type declaringType = typeof(CompactRepositorySelector);

        #endregion Private Static Fields

        /// <summary>
        /// Event to notify that a logger repository has been created.
        /// </summary>
        /// <value>
        /// Event to notify that a logger repository has been created.
        /// </value>
        /// <remarks>
        /// <para>
        /// Event raised when a new repository is created.
        /// The event source will be this selector. The event args will
        /// be a <see cref="LoggerRepositoryCreationEventArgs"/> which
        /// holds the newly created <see cref="ILoggerRepository"/>.
        /// </para>
        /// </remarks>
        public event LoggerRepositoryCreationEventHandler LoggerRepositoryCreatedEvent
        {
            add { m_loggerRepositoryCreatedEvent += value; }
            remove { m_loggerRepositoryCreatedEvent -= value; }
        }

        /// <summary>
        /// Notify the registered listeners that the repository has been created
        /// </summary>
        /// <param name="repository">The repository that has been created</param>
        /// <remarks>
        /// <para>
        /// Raises the <event cref="LoggerRepositoryCreatedEvent">LoggerRepositoryCreatedEvent</event>
        /// event.
        /// </para>
        /// </remarks>
        protected virtual void OnLoggerRepositoryCreatedEvent(ILoggerRepository repository)
        {
            LoggerRepositoryCreationEventHandler handler = m_loggerRepositoryCreatedEvent;
            if (handler != null)
            {
                handler(this, new LoggerRepositoryCreationEventArgs(repository));
            }
        }
    }
}
